package com.nightpiter.controller.log;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
import java.util.logging.LogRecord;

import android.content.Context;

import com.nightpiter.NightPiter;

/**
 * Simple suitable Logger class
 * 
 * @author Ilya Shurigyn (honorik@gmail.com)
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
public class Logger {
	/**
	 * Application context
	 */
	private static NightPiter context;

	/**
	 * logging on/off switcher
	 */
	private static boolean logsEnabled = true;

	/**
	 * in this mode log's saves in file after any new record this mode may
	 * strongly slows program execution
	 * 
	 * @param mode -
	 *            true to switch on debug mode false to switch off
	 */
	private static boolean autoSaveMode = false;

	/**
	 * in this mode log's being translated only in LogCat system
	 */
	private static boolean onlyLogCatMode = true;

	public static final String WARNING = "WARNING";
	public static final String NOTICE = " NOTICE";
	public static final String ERROR = "  ERROR";
	public static final String DEFAULT = NOTICE;
	public static final String EXCEPTION = "EXCEPTION";
	private static final String TIMER = "  TIMER";

	/**
	 * list to serve log records (used if onlyLogCatMode disabled)
	 */
	private static List<LogRecord> log = new LinkedList<LogRecord>();

	/**
	 * stack structure to organize time profiling
	 */
	private static Stack<Long> profilerTimeStack = new Stack<Long>();

	/**
	 * pseudo-tree organization
	 */
	private static String treePrefix = "";

	private static final String TREE_ELEMENT = " |";

	/**
	 * write to log with ERROR tag
	 * 
	 * @param message
	 *            the message to log
	 * @return the log record time in millis
	 */
	public static long writeError(String message) {
		return log(ERROR, message);
	}

	/**
	 * write to log with WARNING tag
	 * 
	 * @param message
	 *            the message to log
	 * @return the log record time in millis
	 */
	public static long writeWarning(String message) {
		return log(WARNING, message);
	}

	/**
	 * write to log with NOTICE tag
	 * 
	 * @param message
	 *            the message to log
	 * @return the log record time in millis
	 */
	public static long writeNotice(String message) {
		return log(NOTICE, message);
	}

	/**
	 * write an exception to log with EXCEPTION tag
	 * 
	 * @param e
	 *            the exception, a message of which will be logged
	 * @return
	 */
	public static long logException(Throwable e) {
		return log(EXCEPTION, e.getMessage());
	}

	/**
	 * write an exception to log with custom tag
	 * 
	 * @param e
	 *            the exception, a message of which will be logged
	 * @return
	 */
	public static void logException(String tag, Throwable e) {
		log(tag, e.getMessage());
	}

	/**
	 * sets autoSaveMode flag true or false; it's strongly recommended to leave
	 * it false
	 * 
	 * @param mode
	 */
	public static void setDebugMode(boolean mode) {
		autoSaveMode = mode;
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param message -
	 *            message to log
	 * @return log record time
	 */
	public static long log(String message) {
		return log(NOTICE, message, false);
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param tag -
	 *            message tag
	 * @param message -
	 *            message to log
	 * @return log record time
	 */
	public static long log(String tag, String message) {
		return log(tag, message, false);
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param object -
	 *            the caller
	 * @param message -
	 *            message to log
	 * @return log record time
	 */
	public static long log(Object object, String message) {
		if (object != null) {
			return log(object.getClass().getSimpleName(), message, false);
		} else {
			return log(NOTICE, message, false);
		}
	}

	/**
	 * logging message saving logging time;
	 * 
	 * @param tag -
	 *            message tag
	 * @param message -
	 *            message to log
	 * @return log record time
	 */
	private static long log(String tag, String message, boolean isProfilerTimer) {
		if (!logsEnabled) {
			return 0;
		}

		long millis = 0;

		String prefix = treePrefix;
		if (isProfilerTimer) {
			prefix += " ";
		} else {
			prefix += "-";
			millis = java.util.Calendar.getInstance().getTimeInMillis();
		}

		if (!onlyLogCatMode) {
			LogRecord record = new java.util.logging.LogRecord(
					java.util.logging.Level.ALL, tag, message);
			record.setMillis(millis);
			log.add(record);
			if (autoSaveMode) {
				saveLog();
			}
		}

		android.util.Log.i("NightPiter", prefix + message);
		return millis;
	}

	/**
	 * save current time for profiling
	 * 
	 * @param message
	 */
	public static void logProfilerBegin(String message) {
		if (!logsEnabled) {
			return;
		}

		profilerTimeStack.push(log(message));
		treePrefix += TREE_ELEMENT;
	}

	/**
	 * retrieves last saved time and logs the difference
	 * 
	 * @param message
	 */
	public static void logProfilerEnd(String message) {
		if (!logsEnabled) {
			return;
		}

		if (profilerTimeStack.isEmpty()) {
			return;
		}

		long time = profilerTimeStack.pop();
		long timeInterval = Math.abs(java.util.Calendar.getInstance()
				.getTimeInMillis()
				- time);
		int depth = profilerTimeStack.size();
		treePrefix = "";
		for (int i = 0; i < depth; i++) {
			treePrefix += TREE_ELEMENT;
		}
		log(NOTICE, timeInterval + " millis (" + message + ")", true);
		log(NOTICE, "", true);
	}

	/**
	 * Calculate time between two log records and write it to log
	 * 
	 * @param start
	 * @param finish
	 * @return time between two records
	 */
	public static long writeTimeInterval(long startRec, long finishRec) {
		long timeInterval = Math.abs(finishRec - startRec);
		log(TIMER, "    Processing time: " + ((float) timeInterval) / 1000
				+ " seconds");
		return timeInterval;
	}

	/**
	 * Calculate time elapsed after the record added and write it to log
	 * 
	 * @param start
	 * @return time has elapsed after the record added
	 */
	public static long writeTimeInterval(long startRec) {
		long timeInterval = Math.abs(java.util.Calendar.getInstance()
				.getTimeInMillis()
				- startRec);
		log(TIMER, "    Processing time: " + ((float) timeInterval) / 1000
				+ " seconds");
		return timeInterval;
	}

	/**
	 * save log to file
	 */
	public static void saveLog() {
		if (onlyLogCatMode) {
			return;
		}

		try {
			FileOutputStream fOut = context.openFileOutput("log.txt",
					Context.MODE_WORLD_READABLE);
			OutputStreamWriter osw = new OutputStreamWriter(fOut);
			for (LogRecord record : log) {
				String date = new Date(record.getMillis()).toString();
				osw.write(date + ":: " + record.getTag() + ":: "
						+ record.getMessage() + '\n');
			}
			osw.flush();
			osw.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}

	}

	/**
	 * @param context
	 *            the context to set
	 */
	public static void openLog(NightPiter context) {
		Logger.context = context;
		log("");
		log("");
		log("-------", "------- Application start -------");
	}

}
